<template>
  <div style="position: relative">
    <div class="verify-img-out">
      <div
        class="verify-img-panel"
        :style="{
          width: setSize.imgWidth,
          height: setSize.imgHeight,
          'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
          'margin-bottom': vSpace + 'px',
        }"
      >
        <div v-show="showRefresh" class="verify-refresh" style="z-index: 3" @click="refresh">
          <i class="iconfont icon-refresh" />
        </div>
        <img
          ref="canvas"
          :src="pointBackImgBase ? 'data:image/png;base64,' + pointBackImgBase : defaultImg"
          alt=""
          style="width: 100%; height: 100%; display: block"
          @click="bindingClick ? canvasClick($event) : undefined"
        />

        <div
          v-for="(tempPoint, index) in tempPoints"
          :key="index"
          class="point-area"
          :style="{
            'background-color': '#1abd6c',
            color: '#fff',
            'z-index': 9999,
            width: '20px',
            height: '20px',
            'text-align': 'center',
            'line-height': '20px',
            'border-radius': '50%',
            position: 'absolute',
            top: parseInt(String(tempPoint.y - 10)) + 'px',
            left: parseInt(String(tempPoint.x - 10)) + 'px',
          }"
        >
          {{ index + 1 }}
        </div>
      </div>
    </div>
    <div
      class="verify-bar-area"
      :style="{
        width: setSize.imgWidth,
        color: barAreaColor,
        'border-color': barAreaBorderColor,
        'line-height': barSize.height,
      }"
    >
      <span class="verify-msg">{{ text }}</span>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, reactive, ref, nextTick, watch } from 'vue'
import { resetSize, aesEncrypt } from '@/utils/AJCaptcha'
import { get, check } from '@/api/captchaController'

interface ImgSize {
  width: string
  height: string
}
interface BarSize {
  width: string
  height: string
}

const props = withDefaults(
  defineProps<{
    mode?: 'pop' | 'fixed'
    captchaType: string
    vSpace?: number
    imgSize?: ImgSize
    barSize?: BarSize
    defaultImg?: string
  }>(),
  {
    mode: 'fixed',
    vSpace: 5,
    imgSize: () => ({ width: '310px', height: '155px' }),
    barSize: () => ({ width: '310px', height: '40px' }),
    defaultImg: '',
  },
)

const emit = defineEmits<{
  (e: 'success', payload: any): void
  (e: 'error', payload: any): void
}>()

const secretKey = ref('')
const checkNum = ref(3)
const fontPos = ref<{ x: number; y: number }[]>([])
const checkPosArr = ref<{ x: number; y: number }[]>([])
const num = ref(1)
const pointBackImgBase = ref('')
const poinTextList = ref<string[]>([])
const backToken = ref('')
const setSize = reactive({
  imgHeight: '0px',
  imgWidth: '0px',
  barHeight: '0px',
  barWidth: '0px',
})
const tempPoints = ref<{ x: number; y: number }[]>([])
const text = ref('')
const barAreaColor = ref<string | undefined>(undefined)
const barAreaBorderColor = ref<string | undefined>(undefined)
const showRefresh = ref(true)
const bindingClick = ref(true)

watch(
  () => props.captchaType,
  () => init(),
  { immediate: true },
)

onMounted(() => {
  ;(document.body as any).onselectstart = function () {
    return false
  }
})

function init() {
  fontPos.value.splice(0, fontPos.value.length)
  checkPosArr.value.splice(0, checkPosArr.value.length)
  num.value = 1
  getPicture()
  nextTick(() => {
    const size = resetSize(props as any)
    Object.assign(setSize, size)
  })
}

function canvasClick(e: MouseEvent) {
  const pos = getMousePos(e)
  checkPosArr.value.push(pos)
  if (num.value === checkNum.value) {
    num.value = createPoint(pos)
    const transformed = pointTransfrom(checkPosArr.value, setSize)
    setTimeout(async () => {
      const captchaVerification = secretKey.value
        ? aesEncrypt(backToken.value + '---' + JSON.stringify(transformed), secretKey.value)
        : backToken.value + '---' + JSON.stringify(transformed)
      const data: any = {
        captchaType: props.captchaType,
        pointJson: secretKey.value
          ? aesEncrypt(JSON.stringify(transformed), secretKey.value)
          : JSON.stringify(transformed),
        token: backToken.value,
      }
      try {
        const res = await check(data)
        if (res.data.repCode === '0000') {
          barAreaColor.value = '#4cae4c'
          barAreaBorderColor.value = '#5cb85c'
          text.value = '验证成功'
          bindingClick.value = false
          // 弹窗自动关闭由父组件处理；此处仅透传成功
          emit('success', { captchaVerification })
        } else {
          emit('error', {})
          barAreaColor.value = '#d9534f'
          barAreaBorderColor.value = '#d9534f'
          text.value = '验证失败'
          setTimeout(() => refresh(), 700)
        }
      } catch (err) {
        console.error('点选验证失败:', err)
      }
    }, 400)
  }
  if (num.value < checkNum.value) {
    num.value = createPoint(pos)
  }
}

function getMousePos(e: MouseEvent) {
  const target = e.target as HTMLElement
  const rect = target.getBoundingClientRect()
  const x = (e as MouseEvent).offsetX ?? e.clientX - rect.left
  const y = (e as MouseEvent).offsetY ?? e.clientY - rect.top
  return { x, y }
}

function createPoint(pos: { x: number; y: number }) {
  tempPoints.value.push({ ...pos })
  return ++num.value
}

function refresh() {
  tempPoints.value.splice(0, tempPoints.value.length)
  barAreaColor.value = '#000'
  barAreaBorderColor.value = '#ddd'
  bindingClick.value = true
  fontPos.value.splice(0, fontPos.value.length)
  checkPosArr.value.splice(0, checkPosArr.value.length)
  num.value = 1
  getPicture()
  text.value = '验证失败'
  showRefresh.value = true
}

async function getPicture() {
  const data = {
    captchaType: props.captchaType,
    clientUid: localStorage.getItem('point') || '',
    ts: Date.now(),
  }
  try {
    // 后端统一为 GET /captcha/get，并通过 type 区分：点选一般为 'clickWord' 或 'point'
    const res = await get({ type: props.captchaType })
    if (res.data.repCode === '0000') {
      pointBackImgBase.value = res.data.repData?.originalImageBase64 || ''
      backToken.value = res.data.repData?.token || ''
      secretKey.value = res.data.repData?.secretKey || ''
      poinTextList.value = res.data.repData?.wordList || []
      text.value = '请依次点击【' + poinTextList.value.join(',') + '】'
    } else {
      text.value = res.data?.repMsg || '加载失败'
    }

    if (res.data.repCode === '6201') {
      pointBackImgBase.value = ''
    }
  } catch (err) {
    console.error('获取点选验证码失败:', err)
  }
}

function pointTransfrom(pointArr: { x: number; y: number }[], imgSize: any) {
  const newPointArr = pointArr.map((p) => {
    const x = Math.round((310 * p.x) / parseInt(imgSize.imgWidth))
    const y = Math.round((155 * p.y) / parseInt(imgSize.imgHeight))
    return { x, y }
  })
  return newPointArr
}

// 暴露方法
defineExpose({ refresh })
</script>

<style scoped>
.verify-img-out {
  position: relative;
}
.verify-img-panel {
  position: relative;
  border: 1px solid #ddd;
  background-repeat: no-repeat;
}
.verify-refresh {
  position: absolute;
  right: 0;
  top: 0;
  width: 30px;
  height: 30px;
  cursor: pointer;
  background: rgba(0, 0, 0, 0.3);
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
}
.verify-refresh .iconfont {
  color: #fff;
  font-size: 16px;
}
.point-area {
}
.verify-bar-area {
  position: relative;
  background: #fff;
  text-align: center;
  box-sizing: content-box;
  border: 1px solid #ddd;
  border-radius: 4px;
}
.verify-msg {
  z-index: 3;
  font-size: 12px;
  color: #45494c;
}
</style>